﻿using System.Data;
using System.IO;
using System.Xml;
using System.Xml.XPath;
using NUnit.Framework;

namespace IPAMS.Data
{
	public class DataMapper : IMapper
	{
		private readonly XPathDocument mappingDocument;
		private readonly XmlNamespaceManager namespaceManager;
		private readonly XPathNavigator navigator;

		public DataMapper(XPathDocument mappingDocument)
		{
			this.mappingDocument = mappingDocument;
			navigator = this.mappingDocument.CreateNavigator();
			namespaceManager = new XmlNamespaceManager(navigator.NameTable);
			namespaceManager.AddNamespace("map", "http://tempuri.org/Mapping.xsd");
		}

		#region IMapper Members

		public string MapTableName(DataTable table)
		{
			string originalTableName = table.TableName.Trim();
			string xpath =
				string.Format("/map:Map/map:Table[map:SourceName='{0}']", originalTableName);
			XPathNavigator translationNode = GetSingleNodeByXPath(xpath);
			if (translationNode == null)
				return originalTableName;
			XPathNavigator targetNameNode =
				translationNode.SelectSingleNode("map:TargetName", namespaceManager);
			if (targetNameNode == null)
				return originalTableName;
			else
				return targetNameNode.Value;
		}

		public string FindOriginalTableName(DataTable table)
		{
			string mappedTableName = table.TableName.Trim();
			string xpath =
				string.Format(
					"/map:Map/map:Table[map:SourceName='{0}' and not(map:TargetName) or map:TargetName='{0}']",
					mappedTableName);
			XPathNavigator translationNode = GetSingleNodeByXPath(xpath);
			if (translationNode == null)
				return mappedTableName;
			return
				translationNode.SelectSingleNode("map:SourceName", namespaceManager).Value;
		}

		public string MapColumnName(DataColumn column)
		{
			XPathNavigator columnTranslationNode =
				GetSingleNodeByXPath(
					string.Format(
						"/map:Map/map:Table[map:SourceName='{0}']/map:Column[map:SourceName='{1}']/map:TargetName",
						column.Table.TableName.Trim(), column.ColumnName.Trim()));
			if (columnTranslationNode == null)
				return column.ColumnName;
			else
				return columnTranslationNode.Value;
		}

		public string FindOriginalColumnName(DataColumn column)
		{
			XPathNavigator columnTranslationNode =
				GetSingleNodeByXPath(
					string.Format(
						"/map:Map/map:Table[map:SourceName='{0}']/map:Column[map:TargetName='{1}']/map:SourceName",
						FindOriginalTableName(column.Table), column.ColumnName.Trim()));
			if (columnTranslationNode == null)
				return column.ColumnName;
			else
				return columnTranslationNode.Value;
		}

		#endregion

		private XPathNavigator GetSingleNodeByXPath(string xpath)
		{
			XPathExpression tableXPath =
				navigator.Compile(
					xpath);
			tableXPath.SetContext(namespaceManager);
			return navigator.SelectSingleNode(tableXPath);
		}
	}

	[TestFixture]
	public class DataMapperTest
	{
		private static DataMapper GetMapper(string mappingXML)
		{
			StringReader reader = new StringReader(mappingXML);
			XPathDocument document = new XPathDocument(reader);
			return new DataMapper(document);
		}

		[Test]
		public void MapColumnName_ColumnHasMapping_ReturnTranslatedName()
		{
			DataMapper mapper =
				GetMapper(
					"<Map xmlns=\"http://tempuri.org/Mapping.xsd\"><Table><SourceName>Table</SourceName><TargetName>TranslatedTable</TargetName><Column><SourceName>ColumnA</SourceName><TargetName>NewColumnName</TargetName></Column></Table></Map>");
			string tableName = "Table";
			DataTable table = new DataTable(tableName);
			DataColumn column = table.Columns.Add("ColumnA");
			Assert.AreEqual("NewColumnName", mapper.MapColumnName(column));
		}

		[Test]
		public void MapColumnName_EmptyMapping_ReturnSameName()
		{
			DataMapper mapper =
				GetMapper("<Map xmlns=\"http://tempuri.org/Mapping.xsd\"/>");
			string tableName = "Table";
			DataTable table = new DataTable(tableName);
			string columnName = "ColumnA";
			DataColumn column = table.Columns.Add(columnName);
			Assert.AreEqual(columnName, mapper.MapColumnName(column));
		}

		[Test]
		public void MapColumnName_TableHasMappingButNotColumn_ReturnSameName()
		{
			DataMapper mapper =
				GetMapper(
					"<Map xmlns=\"http://tempuri.org/Mapping.xsd\"><Table><SourceName>Table</SourceName><TargetName>TranslatedTable</TargetName></Table></Map>");
			string tableName = "Table";
			DataTable table = new DataTable(tableName);
			string columnName = "ColumnA";
			DataColumn column = table.Columns.Add(columnName);
			Assert.AreEqual(columnName, mapper.MapColumnName(column));
		}

		[Test]
		public void MapColumnName_TableHasNoMapping_ReturnSameName()
		{
			DataMapper mapper =
				GetMapper(
					"<Map xmlns=\"http://tempuri.org/Mapping.xsd\"><Table><SourceName>OtherTable</SourceName></Table></Map>");
			string tableName = "Table";
			DataTable table = new DataTable(tableName);
			string columnName = "ColumnA";
			DataColumn column = table.Columns.Add(columnName);
			Assert.AreEqual(columnName, mapper.MapColumnName(column));
		}

		[Test]
		public void
			MapTableName_TableWithNameThatAppearsWithoutTranslation_ReturnSameName()
		{
			DataMapper mapper =
				GetMapper(
					"<Map xmlns=\"http://tempuri.org/Mapping.xsd\"><Table><SourceName>Table</SourceName></Table></Map>");
			string tableName = "Table";
			DataTable table = new DataTable(tableName);
			Assert.AreEqual(tableName, mapper.MapTableName(table));
		}

		[Test]
		public void MapTableName_TableWithNameThatDoesntExist_ReturnSameName()
		{
			DataMapper mapper =
				GetMapper("<Map xmlns=\"http://tempuri.org/Mapping.xsd\"/>");
			string tableName = "Table";
			DataTable table = new DataTable(tableName);
			Assert.AreEqual(tableName, mapper.MapTableName(table));
		}

		[Test]
		public void MapTableName_TableWithNameThatHasTranslation_ReturnTranslatedName()
		{
			DataMapper mapper =
				GetMapper(
					"<Map xmlns=\"http://tempuri.org/Mapping.xsd\"><Table><SourceName>Table</SourceName><TargetName>TranslatedTable</TargetName></Table></Map>");
			DataTable table = new DataTable("Table");
			Assert.AreEqual("TranslatedTable", mapper.MapTableName(table));
		}
	}
}